SECTION header vstart=0
    program_length dd program_end
    code_entry  dw start
                dd section.code.start
    
    realloc_tbl_len dw (header_end-realloc_begin)/4

    realloc_begin:
    code_segment    dd section.code.start
    data_segment    dd section.data.start
    stack_segment   dd section.stack.start
header_end:

SECTION code align=16 vstart=0
new_int_0x70:
    push ax
    push bx
    push cx
    push dx
    push es

.w0:
    mov al,0x0a                     ;要访问cmos的寄存器a，把寄存器a写入al寄存器
    or  al,0x08                     ;把NMI屏蔽，防止触发不可平不中断
    out 0x70,al                     ;把0x0A|0x08写入0x70端口，表示要读取寄存器a
    in  al,0x71                     ;从端口0x71处读取出寄存器A的值
    test al,0x80                    ;检测最高位bit7，为1的时候说明RTC正在更新时间，为0表示可以安全读取
    jnz .w0                         ;不是0，反复等待

    xor al,al                       ;走到这里，说明更新时间完毕，把al寄存器清零，说明要访问CMOS寄存器0（秒寄存器）
    or  al,0x80                     ;把NMI响应关掉，防止被其他中断打断
    out 0x70,al                     ;把要访问分钟的信息写入0x70端口
    in  al,0x71                     ;从0x71端口读出分钟的数据
    push ax                         ;把分钟的数据压栈

    mov al,2                        ;al目前是2.CMOS中的二号寄存器，存放着分钟的数据
    or  al,0x80                     ;关闭NMI端口，防止中断放生
    out 0x70,al                     ;把要访问二号寄存器的信息写入0x70端口
    in al,0x71                      ;从0x71端口读出分钟的数据并放入al寄存器
    push ax                         ;将分钟的数据压栈

    mov al,4                        ;目前al = 4，表示要访问CMOS中的寄存器4，小时寄存器
    or al,0x80                      ;al|0x80，表示关闭NMI接口
    out 0x70,al                     ;通过0x70端口，把要访问寄存器4的信息写入
    in al,0x71                      ;通过0x71端口，读出小时的数据并放入al寄存器
    push ax                         ;将小时数据压栈

    mov al,0x0c                     ;要访问RTC中的寄存器C
    out 0x70,al                     ;写入0x70端口
    in al,0x71                      ;通过0x71端口读了一次RTC的寄存器C，副作用是寄存器C的中断标志会清零

    mov ax,0xb800                   ;设置ax为0xb800，显存段寄存器
    mov es,ax                       ;把显存段寄存器写入es寄存器

    pop ax                          ;数据出栈，ax = 时
    call bcd_to_ascii               ;把ax中的数据变成ASCII编码
    mov bx,12*160 + 36*2            ;把bx寄存器写入屏幕位置的坐标，12行，36列

    mov [es:bx],ah                  ;把高字节数据放入对应的内存
    mov [es:bx+2],al                ;把低字节数据放入对应的内存

    mov al,':'                      ;把冒号传入al寄存器中
    mov [es:bx+4],al                ;在对应显存中显示冒号
    not byte [es:bx+5]              ;反转冒号的颜色

    pop ax                          ;ax寄存器中的是分
    call bcd_to_ascii               ;把分钟由bcd数据转换成ascii码
    mov [es:bx+6],ah                ;显示分钟的十位
    mov [es:bx+8],al                ;显示分钟的个位

    mov al,':'                      ;显示冒号                    
    mov [es:bx+10],al               ;把冒号放入对应的显存
    not byte [es:bx+11]             ;反转冒号的颜色

    pop ax                          ;ax寄存器中的是秒
    call bcd_to_ascii               ;把秒由bcd数据转换成ascii码
    mov [es:bx+12],ah               ;显示秒的十位
    mov [es:bx+14],al               ;显示秒的个位


    mov al,0x20
    out 0xa0,al                     ;向从片（0xa0）发送中断结束信号
    out 0x20,al                     ;向主片（0x20）发送中断结束信号

    pop es
    pop dx
    pop cx
    pop bx
    pop ax

    iret                            ;结束中断程序

bcd_to_ascii:
    mov ah,al                               ;al寄存器中存放着时，分，秒的信息，把al中的值赋值给ah
    and al,0x0f                             ;保留al寄存器中的低四位
    add al,0x30                             ;al + 0x30，把al寄存器中的值变成ASCII编码
    shr ah,4                                ;右移ah寄存器4位，保留高四位
    and ah,0x0f                             ;保留ah寄存器中的高四位
    add ah,0x30                             ;al + 0x30，把ah寄存器中的值变成ASCII编码

    ret                                     ;返回call调用

start:
    mov ax,[stack_segment]
    mov ss,ax
    mov sp,ss_pointer
    mov ax,[data_segment]
    mov ds,ax                               ;初始化各个段寄存器

    mov bx,init_msg                         ;把init_msg段的偏移地址写入bx寄存器
    call put_string                         ;输出字符

    mov al,0x70                             ;要改变0x70号中断
    mov bl,4                                ;bl = 4
    mul bl                                  ;中断号*4，知道了中断函数的段地址和偏移地址
    mov bx,ax                               ;把bx=中断函数的段地址和偏移地址

    cli                                     ;中断位if位清零，屏蔽可屏蔽中断

    push es
    mov ax,0x0000
    mov es,ax
    mov word [es:bx],new_int_0x70
    mov word [es:bx+2],cs 
    pop es                                  ;把自己定义的中断函数写入中断函数表

    mov al,0x0b
    or al,0x80
    out 0x70,al
    mov al,0x12
    out 0x71,al                             ;配置 RTC 的寄存器 B，启用 更新结束中断 (UIE)，并设置为 24 小时制

    mov al,0x0c                             ;要访问RTC的寄存器C
    out 0x70,al
    in al,0x71                              ;读RTC寄存器C，复位未决的中断状态

    in al,0xa1
    and al,0xfe
    out 0xa1,al
                                            ;这段代码就是 在从片 PIC 上打开 IRQ8 (RTC 的中断请求线)，让 RTC 中断能够真正传到 CPU。
    sti 

    mov bx,done_msg
    call put_string

    mov bx,tips_msg
    call put_string

    mov cx,0xb800
    mov ds,cx
    mov byte [12*160 + 33*2],'@'

.idle
    hlt
    not byte [12*160 + 33*2+1]
    jmp .idle

put_string:
    mov cl,[bx]                              ;cl寄存器中存放着msg中的字符
    or cl,cl                                 ;检查cl寄存器的值是否是0
    jz .exit                                 ;是0，跳转到exit
    call put_char                            ;不是0，输出这个字符
    inc bx                                   ;递增到下一个字符
    jmp put_string                           ;直到0位置，反复输出对应的字符

    .exit:
        ret                                 ;返回call调用

put_char:
    push ax
    push bx
    push cx
    push dx
    push ds
    push es                                  ;将各个寄存器压栈

    mov dx,0x3d4                             ;0x3d4，显卡中的索引寄存器
    mov al,0x0e                              ;0x0e，显示器光标中的低字节
    out dx,al                                ;告诉显卡要访问光标的低字节
    mov dx,0x3d5                             ;要从0x3d5端口读出光标数据
    in al,dx                                 ;读出光标低字节数据，放入al寄存器
    mov ah,al                                ;把光标低字节数据放入ah寄存器

    mov dx,0x3d4                            ;0x3d4，显卡中的索引寄存器
    mov al,0x0f                             ;0x0f，显示器光标中的高字节
    out dx,al                               ;告诉显卡要访问光标的高字节
    mov dx,0x3d5                            ;要从0x3d5端口读出光标数据
    in al,dx                                ;读出光标高字节数据，放入al寄存器
    mov bx,ax                               ;此时ax寄存器中存入的是完整的光标数据，放入bx寄存器

    cmp cl,0x0d                             ;检查cl中的字符是否是回车符
    jnz .put_0a                             ;不是回车符，去检查是否是换行符
    mov ax,bx                               ;既不是回车，也不是换行，继续执行，ax=bx
    mov bl,80                               ;bl = 80（一行有80列）
    div bl                                  ;处以80，得知光标在第几行
    mul bl                                  ;乘以80，得知光标的起始列的位置
    mov bx,ax                               ;把光标的起始列的位置放入bx寄存器
    jmp .set_cursor                         ;跳转到set_cursor

.put_0a:
    cmp cl,0x0a                             ;检查是否是换行符
    jnz .put_char                           ;不是换行符，跳转到put_char函数
    add bx,80                               ;是换行符，把当前光标位置（bx）加一行
    jmp .roll_screen                        ;跳转到是否要回滚屏幕

.put_other
    mov ax,0xb800                           ;把显存段地址放入ax寄存器
    mov es,ax                               ;把显存段地址放入es寄存器
    shl bx,1                                ;bx * 2，在显存中光标显示正确的位置
    mov [es:bx],cl                          ;把字符放入正确的位置

    shr bx,1                                ;除回去
    add bx,1                                ;光标显示到下一个位置

.roll_screen:
    cmp bx,2000                             ;检查光标位置是否占满一个屏幕
    jl  .set_cursor                         ;小于，没有占满，跳转到set_cursor

    push bx                                 ;大于等于，占满了，先保存当前光标位置
    
    mov ax,0xb800                           ;ax = 0xb800
    mov ds,ax                               ;ds = 0xb800
    mov es,ax                               ;es = 0xb8--
    cld                                     ;清空df位，让movsw指令递增移动
    mov si,0xa0                             ;将si指向第二行
    mov di,0x00                             ;将di指向第一行
    mov cx,1920                             ;24 行 × 160 字节 = 3840 字节，
                                            ;换算成字(2字节) → 3840 ÷ 2 = 1920 个字
    rep movsw                               ;把后24行的内容向上提一行
    mov bx,3840                             ;第25行的起始位置
    mov cx,80                               ;一行一共80列
.cls:
    mov word[es:bx],0x0720                  ;设置字符颜色，黑色，放入第二十五行的显存
    add bx,2                                ;bx += 2，去设置下一个字
    loop .cls                               ;重复80次，清空第25行

    pop bx                                  ;弹出bx的原值
    sub bx,80                               ;因为向上移动了一行，所以bx - 80

.set_cursor:
    mov dx,0x3d4
    mov al,0x0e
    out dx,al
    mov dx,0x3d5
    mov al,bh
    out dx,al
    mov dx,0x3d4
    mov al,0x0f
    out dx,al
    mov dx,0x3d5
    mov al,bl
    out dx,al
                                            ;以上是把算好的光标坐标显示到屏幕上
    pop es
    pop ds
    pop dx
    pop cx
    pop bx
    pop ax

    ret
SECTION data align=16 vstart=0

    init_msg       db 'Starting...',0x0d,0x0a,0
                   
    inst_msg       db 'Installing a new interrupt 70H...',0
    
    done_msg       db 'Done.',0x0d,0x0a,0

    tips_msg       db 'Clock is now working.',0
                   
;===============================================================================
SECTION stack align=16 vstart=0
           
                 resb 256
ss_pointer:
 
;===============================================================================
SECTION program_trail
program_end: